/****************************************************************************************** Import */
import _ from 'lodash';
import React, {Component} from 'react';
import {View} from 'react-native';
import xmldom from 'xmldom';
import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource';
import Svg, {
    Circle,
    Ellipse,
    G ,
    LinearGradient,
    RadialGradient,
    Line,
    Path,
    Polygon,
    Polyline,
    Rect,
    Text,
    TSpan,
    Defs,
    Stop
} from 'react-native-svg';

/****************************************************************************************** Utilities */
/**
 * 1、驼峰命名
 * 2、驼峰方式修改节点名
 * 3、从节点值中删除像素字符串(px)
 * 4、获取可用的attribute
 * 5、转换style
 * 6、固定Y位置
 */
const camelCase = value => value.replace(/-([a-z])/g, g => g[1].toUpperCase());
const camelCaseNodeName = ({nodeName, nodeValue}) => ({nodeName: camelCase(nodeName), nodeValue});
const removePixelsFromNodeValue = ({nodeName, nodeValue}) => ({nodeName, nodeValue: nodeValue.replace('px', '')});
const getEnabledAttributes = enabledAttributes => ({nodeName}) => enabledAttributes.includes(camelCase(nodeName));
const transformStyle = ({nodeName, nodeValue, fillProp}) => {
  if (nodeName === 'style') {
    return nodeValue.split(';')
      .reduce((acc, attribute) => {
        const [property, value] = attribute.split(':');
        if (property == "")
            return acc;
        else
            return {...acc, [camelCase(property)]: fillProp && property === 'fill' ? fillProp : value};
      }, {});
  }
  return null;
};
function fixYPosition (y, node) {
    if (node.attributes) {
        const fontSizeAttr = Object.keys(node.attributes).find(a => node.attributes[a].name === 'font-size');
        if (fontSizeAttr) {
            return '' + (parseFloat(y) - parseFloat(node.attributes[fontSizeAttr].value));
        }
    }
    if (!node.parentNode) {
        return y;
    }
    return fixYPosition(y, node.parentNode)
}

/******************************************************************************************
 * Variable
 ******************************************************************************************/

// 可以接受的Svg元素类型
const ACCEPTED_SVG_ELEMENTS = [
    'svg',
    'g',
    'circle',
    'path',
    'rect',
    'defs',
    'line',
    'linearGradient',
    'radialGradient',
    'stop',
    'ellipse',
    'polygon',
    'polyline',
    'text',
    'tspan'
];

// Attributes from SVG elements that are mapped directly.
const SVG_ATTS = ['viewBox', 'width', 'height'];
const G_ATTS = ['id'];
const CIRCLE_ATTS = ['cx', 'cy', 'r'];
const PATH_ATTS = ['d'];
const RECT_ATTS = ['width', 'height'];
const LINE_ATTS = ['x1', 'y1', 'x2', 'y2'];
const LINEARG_ATTS = LINE_ATTS.concat(['id', 'gradientUnits']);
const RADIALG_ATTS = CIRCLE_ATTS.concat(['id', 'gradientUnits']);
const STOP_ATTS = ['offset'];
const ELLIPSE_ATTS = ['cx', 'cy', 'rx', 'ry'];
const TEXT_ATTS = ['fontFamily', 'fontSize', 'fontWeight', 'textAnchor']
const POLYGON_ATTS = ['points'];
const POLYLINE_ATTS = ['points'];
const COMMON_ATTS = ['fill', 'fillOpacity', 'stroke', 'strokeWidth', 'strokeOpacity', 'opacity',
	'strokeLinecap', 'strokeLinejoin','strokeDasharray', 'strokeDashoffset', 
	'x', 'y', 'rotate', 'scale', 'origin', 'originX', 'originY', 'transform', 'clipPath'];

// 全局index
let ind = 0;

/******************************************************************************************
 * Export
 ******************************************************************************************/

export default class BaseSvg extends Component{

	/**
	 * 初始化
	 */
    constructor(props){
        super(props);
        this.state = {
			fill: props.fill, 
			svgXmlData: props.svgXmlData
		};

        this.createSVGElement     = this.createSVGElement.bind(this);
        this.obtainComponentAtts  = this.obtainComponentAtts.bind(this);
        this.inspectNode          = this.inspectNode.bind(this);
        this.fetchSVGData         = this.fetchSVGData.bind(this);

        this.isComponentMounted   = false;

        // Gets the image data from an URL or a static file
        if (props.source) {
            const source = resolveAssetSource(props.source) || {};
            this.fetchSVGData(source.uri);
        }
    }

	/**
	 * 开始挂载
	 */
    componentWillMount() {
        this.isComponentMounted = true;
    }

	/**
	 * props属性改变时
	 */
    componentWillReceiveProps (nextProps){
        if (nextProps.source) {
            const source = resolveAssetSource(nextProps.source) || {};
            const oldSource = resolveAssetSource(this.props.source) || {};
            if(source.uri !== oldSource.uri){
            this.fetchSVGData(source.uri);
            }
        }

        if (nextProps.svgXmlData !== this.props.svgXmlData) {
            this.setState({ svgXmlData: nextProps.svgXmlData });
        }

        if (nextProps.fill !== this.props.fill) {
            this.setState({ fill: nextProps.fill });
        }
    }

	/**
	 * 即将卸载时
	 */
    componentWillUnmount() {
        this.isComponentMounted = false
    }

	/**
	 * 获取svg数据
	 */
    async fetchSVGData(uri) {
        let responseXML = null, error = null;
        try {
            const response = await fetch(uri);
            responseXML = await response.text();
        } catch(e) {
            error = e;
            console.error("ERROR SVG", e);
        } finally {
            if (this.isComponentMounted) {
                this.setState({ svgXmlData: responseXML }, () => {
                    const { onLoad } = this.props;
                    if (onLoad && !error) {
                    onLoad();
                    }
                });
            }
        }

        return responseXML;
    }

	/**
	 * Remove empty strings from children array
	 */
    trimElementChilden(children) {
        for (child of children) {
            if (typeof child === 'string') { 
				if (child.trim().length === 0) {
					children.splice(children.indexOf(child), 1);
				}	
            }
        }
    }

	/**
	 * 创建Svg元素
	 * 
	 * @param {*} node 
	 * @param {*} childs 
	 */
    createSVGElement(node, childs){
        this.trimElementChilden(childs);
        let componentAtts = {};
        const i = ind++;
        switch (node.nodeName) {
            case 'svg':
                componentAtts = this.obtainComponentAtts(node, SVG_ATTS);
                if (this.props.width) {
                	componentAtts.width = this.props.width;
                }
                if (this.props.height) {
                	componentAtts.height = this.props.height;
                }
                return <Svg key={i} {...componentAtts}>{childs}</Svg>;
            case 'g':
                componentAtts = this.obtainComponentAtts(node, G_ATTS);
                return <G key={i} {...componentAtts}>{childs}</G>;
            case 'path':
                componentAtts = this.obtainComponentAtts(node, PATH_ATTS);
                return <Path key={i} {...componentAtts}>{childs}</Path>;
            case 'circle':
                componentAtts = this.obtainComponentAtts(node, CIRCLE_ATTS);
                return <Circle key={i} {...componentAtts}>{childs}</Circle>;
            case 'rect':
                componentAtts = this.obtainComponentAtts(node, RECT_ATTS);
                return <Rect key={i} {...componentAtts}>{childs}</Rect>;
            case 'line':
                componentAtts = this.obtainComponentAtts(node, LINE_ATTS);
                return <Line key={i} {...componentAtts}>{childs}</Line>;
            case 'defs':
                return <Defs key={i}>{childs}</Defs>;
            case 'linearGradient':
                componentAtts = this.obtainComponentAtts(node, LINEARG_ATTS);
                return <LinearGradient key={i} {...componentAtts}>{childs}</LinearGradient>;
            case 'radialGradient':
                componentAtts = this.obtainComponentAtts(node, RADIALG_ATTS);
                return <RadialGradient key={i} {...componentAtts}>{childs}</RadialGradient>;
            case 'stop':
                componentAtts = this.obtainComponentAtts(node, STOP_ATTS);
                return <Stop key={i} {...componentAtts}>{childs}</Stop>;
            case 'ellipse':
                componentAtts = this.obtainComponentAtts(node, ELLIPSE_ATTS);
                return <Ellipse key={i} {...componentAtts}>{childs}</Ellipse>;
            case 'polygon':
                componentAtts = this.obtainComponentAtts(node, POLYGON_ATTS);
                return <Polygon key={i} {...componentAtts}>{childs}</Polygon>;
            case 'polyline':
                componentAtts = this.obtainComponentAtts(node, POLYLINE_ATTS);
                return <Polyline key={i} {...componentAtts}>{childs}</Polyline>;
            case 'text':
                componentAtts = this.obtainComponentAtts(node, TEXT_ATTS);
                return <Text key={i} {...componentAtts}>{childs}</Text>;
            case 'tspan':
                componentAtts = this.obtainComponentAtts(node, TEXT_ATTS);
                if (componentAtts.y) {
                	componentAtts.y = fixYPosition(componentAtts.y, node)
                }
                return <TSpan key={i} {...componentAtts}>{childs}</TSpan>;
            default:
                return null;
        }
    }

	/**
	 * 获取组件Attributes
	 * 
	 * @param {*} param0 
	 * @param {*} enabledAttributes 
	 */
    obtainComponentAtts({attributes}, enabledAttributes) {
        const styleAtts = {};

        if (this.state.fill && this.props.fillAll) {
            styleAtts.fill = this.state.fill;
        }

        Array.from(attributes).forEach(({nodeName, nodeValue}) => {
            Object.assign(styleAtts, transformStyle({
                nodeName,
                nodeValue,
                fillProp: this.state.fill
            }));
        });

        const componentAtts =  Array.from(attributes)
		.map(camelCaseNodeName)
		.map(removePixelsFromNodeValue)
		.filter(getEnabledAttributes(enabledAttributes.concat(COMMON_ATTS)))
		.reduce((acc, {nodeName, nodeValue}) => {
			acc[nodeName] = (this.state.fill && nodeName === 'fill' && nodeValue !== 'none') ? this.state.fill : nodeValue
			return acc
		}, {});
        Object.assign(componentAtts, styleAtts);
        return componentAtts;
    }

	/**
	 * 检查节点
	 * 
	 * @param {*} node 
	 */
    inspectNode(node){
        // Only process accepted elements
        if (!ACCEPTED_SVG_ELEMENTS.includes(node.nodeName)) {
            return (<View key={0} />);
        }

        // Process the xml node
        const arrayElements = [];

        // if have children process them.
        // Recursive function.
        if (node.childNodes && node.childNodes.length > 0){
            for (let i = 0; i < node.childNodes.length; i++){
                const isTextValue = node.childNodes[i].nodeValue
                if (isTextValue) {
                    arrayElements.push(node.childNodes[i].nodeValue)
                } else {
                    const nodo = this.inspectNode(node.childNodes[i]);
                    if (nodo != null) {
                        arrayElements.push(nodo);
                    }
                }
            }
        }
        return this.createSVGElement(node, arrayElements);
    }

    /**
     * 渲染
     */
    render () {
        try {
            if (this.state.svgXmlData == null) {
                return null;
            }

            const inputSVG = this.state.svgXmlData.substring(
                this.state.svgXmlData.indexOf("<svg "),
                (this.state.svgXmlData.indexOf("</svg>") + 6)
            ).replace(/<!-(.*?)->/g, '');

            const doc = new xmldom.DOMParser().parseFromString(inputSVG);

            const rootSVG = this.inspectNode(doc.childNodes[0]);

            return(
                <View style={this.props.style}>
                    {rootSVG}
                </View>
            );
        } catch(e){
            console.error("ERROR SVG", e);
            return null;
        }
    }
}
